﻿using DefaultDspPlugins.UI.Common.Modulizer.Engine.GUI;
using DefaultDspPlugins.UI.Common.Modulizer.Engine.GUI.Modules;
using DefaultDspPlugins.UI.Common.Modulizer.Engine.GUIv2.FrontEndComponents;
using SharpDepend.Datatypes;
using SharpDepend.Storage;
using SoundEngine3.DspPlugins.Meta;
using System;
using System.Collections.Generic;
using ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer;
using ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine;
using ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine.Modules.Container;
using ZquenceStudio3Host.Graphics.Components;
using ZquenceStudio3Host.Graphics.GUI;
using ZquenceStudio3Host.Resources;

namespace DefaultDspPlugins.UI.Common.Modulizer.Engine.GUIv2.Editors
{
    public class FrontEndPanel : ZoomablePanel
    {
        internal static Button GetEditButton(BasicGuiManager engineCore)
        {
            Button edit = GetBaseButton(engineCore);
            edit.setTextureIcon("Images/Icons/tools.png");

            return edit;
        }

        internal static Button GetBaseButton(BasicGuiManager engineCore)
        {
            Button edit = new Button(engineCore);
            edit.DimensionInProcent = false;
            edit.Width = 32;
            edit.Height = 32;
            edit.BackgroundColor = GlobalColors.ButtonColor;

            return edit;
        }

        private class ModuleUIPanelWrapped : Panel
        {
            public ModuleUIPanel Panel;
            public Button Edit;

            public ModuleUIPanelWrapped(BasicGuiManager engineCore, ModuleUIPanel panel, bool isPart) : base(engineCore, isPart)
            {
                BackgroundColor = Color.Transparent;

                Panel = panel;
                panel.DimensionInProcent = true;
                panel.Width = 100;
                panel.Height = 100;
                AddComponent(panel);

                Edit = GetEditButton(EngineCore);
                Edit.Tag = panel;
                //AddComponent(Edit);
            }
        }

        private Dictionary<Type, Type> mModulesUIMaps = new Dictionary<Type, Type>();
        private Dictionary<BaseModule, ModuleUIPanel> mModules = new Dictionary<BaseModule, ModuleUIPanel>();
        private Dictionary<BaseModule, ModuleUIPanelWrapped> mModules2 = new Dictionary<BaseModule, ModuleUIPanelWrapped>();
        private Dictionary<BaseModuleUI, ModuleUIPanel> mModulesUI = new Dictionary<BaseModuleUI, ModuleUIPanel>();
        private List<ModuleUIPanel> mModuleUIPanels = new List<ModuleUIPanel>();
        private Panel mContent;
        private int mRows = 4;
        private int mColumns = 4;
        private ModulizerUIv2 mModulizerUI;
        private int mNextUIPosition = 0;
        private Button mFrontEndEditButton;
        private bool mDoNotUpdate;
        private List<BaseModule> mVisibleModules = new List<BaseModule>();

        /// <summary>
        /// Get or set the automation of grids rows and columns.
        /// <para>Properties Rows and Columns will be ignored if this is set to true.</para>
        /// </summary>
        public bool AutoGrid { get; set; }

        public int Rows
        {
            get { return mRows; }
            set
            {
                mRows = value;
                UpdateLayout();
            }
        }

        public int Columns
        {
            get { return mColumns; }
            set
            {
                mColumns = value;
                UpdateLayout();
            }
        }

        public IList<ModuleUIPanel> UIModules { get { return mModuleUIPanels; } }

        public FrontEndPanel(BasicGuiManager engineCore, ModulizerUIv2 modulizerUI)
            : base(engineCore)
        {
            AutoGrid = true;

            mModulizerUI = modulizerUI;

            mContent = new Panel(engineCore);
            mContent.BackgroundColor = Color.DarkGray;
            //mContent.SetTexture("Images/TextilBackgroundBlack.png");
            Content = mContent;
            Events.OnScaleChanged += Events_OnScaleChanged;

            mFrontEndEditButton = GetEditButton(engineCore);
            mFrontEndEditButton.Tag = this;
            mFrontEndEditButton.OnClicked += EditButton_OnMousePressing;
            //AddPart(mFrontEndEditButton);

            RegisterModuleUI<CustomWaveformModuleUI>();
            RegisterModuleUI<CustomAlgorithmModuleUI>();
            //RegisterModuleUI<Parameter1ModuleUI>();
        }

        public void RegisterModuleUI<T>() where T : BaseModuleUI
        {
            object[] attributes = typeof(T).GetCustomAttributes(typeof(ModuleUIAttribute), true);
            foreach (var attribute in attributes)
            {
                if (attribute is ModuleUIAttribute)
                {
                    Type module = (attribute as ModuleUIAttribute).ModuleType;

                    mModulesUIMaps.Add(module, typeof(T));

                    return;
                }
            }

            throw new Exception("Can only register ModuleUI with an valid ModuleUIAttribute");
        }

        public ModuleUIPanel AddModule(BaseModule module, bool isProtected)
        {
            BaseModuleUI moduleUi = null;
            if (mModulesUIMaps.ContainsKey(module.GetType()))
            {
                // Found matched ui.
                moduleUi = (BaseModuleUI)Activator.CreateInstance(mModulesUIMaps[module.GetType()], module);
            }
            else
            {
                // Set default ui if no ui is found.
                moduleUi = new DefaultModule(module);
            }
            
            ModuleUIPanel moduleUIPanel = InternalAddModuleUIPanel(moduleUi, isProtected);

            if (!isProtected)
            {
                moduleUi.LayoutChanged += ModuleUi_LayoutChanged;
                //moduleUIPanel.LoadStructure();
                moduleUi.Row = mNextUIPosition / Columns;
                moduleUi.Column = mNextUIPosition % Columns;
                mNextUIPosition++;
                UpdateLayout();
            }

            return moduleUIPanel;
        }
        
        public void SetVisibility(BaseModule module, bool visible)
        {
            if (!mModules2.ContainsKey(module))
            {
                throw new Exception("Can not set visibility on an module that not may exist.");
            }
            if (visible)
            {
                if (!mVisibleModules.Contains(module))
                {
                    mVisibleModules.Add(module);
                    var moduleUI = mModules2[module];
                    moduleUI.Visible = true;
                }
            }
            else
            {
                if (mVisibleModules.Contains(module))
                {
                    mVisibleModules.Remove(module);
                    var moduleUI = mModules2[module];
                    moduleUI.Visible = false;
                }
            }

            if (!mDoNotUpdate)
            {
                UpdateLayout();
            }
        }

        public bool GetVisibility(BaseModule module)
        {
            // TODO: Fixa så att man kollar ifall det är en top module. För det är bara dom som kan vara variabla med denna method.
            // Annars kommer UIn att spara visibility även fast top-moduler har blivit en grupp och inte är en top-modul längre. Då sparas visibility endå och försöker användas endå...
            // Se try-catch methoden som wrappas runt SetVisibility()...
            return mVisibleModules.Contains(module);
        }

        private ModuleUIPanel InternalAddModuleUIPanel(BaseModuleUI moduleUi, bool isProtected)
        {
            ModuleUIPanel moduleUIPanel = new ModuleUIPanel(EngineCore, moduleUi, mModulizerUI);
            mModules.Add(moduleUi.Module, moduleUIPanel);

            if (!isProtected)
            {
                ModuleUIPanelWrapped wrapped = new ModuleUIPanelWrapped(EngineCore, moduleUIPanel, true);
                wrapped.DimensionInProcent = true;

                mModulesUI.Add(moduleUi, moduleUIPanel);
                moduleUIPanel.BackgroundColor = Color.White;
                mContent.AddPart(wrapped);
                mModuleUIPanels.Add(moduleUIPanel);
                mModules2.Add(moduleUi.Module, wrapped);

                wrapped.Edit.Tag = moduleUIPanel;
                wrapped.Edit.OnClicked += EditButton_OnMousePressing;

                mDoNotUpdate = true;
                SetVisibility(moduleUi.Module, true);
                mDoNotUpdate = false;
            }

            return moduleUIPanel;
        }

        internal bool IsProtected(BaseModuleUI moduleUI)
        {
            return !mModulesUI.ContainsKey(moduleUI);
        }

        public void RemoveModule(BaseModule module, bool isProtected)
        {
            if (mModules.ContainsKey(module))
            {
                ModuleUIPanel moduleUIPanel = mModules[module];

                mModules.Remove(module);

                if (!isProtected)
                {
                    mModulesUI.Remove(moduleUIPanel.ModuleUI);

                    SetVisibility(module, false);

                    if (mModules2.ContainsKey(module))
                    {
                        mContent.RemovePart(mModules2[module]);
                        mModules2.Remove(module);

                        mModuleUIPanels.Remove(moduleUIPanel);
                    }
                }
            }
        }
        
        private void EditButton_OnMousePressing(object sender, double x, double y, int id)
        {
            Button button = sender as Button;
            if (button.Tag is ModuleUIPanel)
            {
                mModulizerUI.ShowModuleUIEditor((button.Tag as ModuleUIPanel).ModuleUI, mModulizerUI);
            }
            else
            {
                mModulizerUI.ShowModuleUIEditor((button.Tag as Panel), mModulizerUI);
            }
        }

        internal void SaveModuleUIPanels(CustomNode moduleUIPanels)
        {
            foreach (ModuleUIPanel moduleUIPanel in mModuleUIPanels)
            {
                CustomNode moduleUINode = new CustomNode(moduleUIPanels);
                moduleUINode.Name = moduleUIPanel.Module.ID.ToString();
                moduleUINode.AddNode(moduleUIPanel.ModuleUI.SerializedLayout);

                // Save some data.
                bool isPublic = GetVisibility(moduleUIPanel.Module);
                moduleUINode.AddProperty("IsPublic", isPublic);
            }
        }

        internal void LoadModuleUIPanels(CustomNode moduleUIPanels, BaseModulizer errorHandler)
        {
            for (int i = 0; i < mModuleUIPanels.Count; i++)
            {
                if (i >= moduleUIPanels.Nodes.Count)
                {
                    errorHandler.AddError("Stored module ui index out of index.");
                }
                else
                {
                    CustomNode moduleUINode = moduleUIPanels.Nodes[i];
                    CustomNode moduleUIPanelNode = moduleUINode.Nodes[0];

                    int moduleId = int.Parse(moduleUINode.Name);
                    BaseModule module = mModulizerUI.mModuleManager.Find(moduleId);

                    if (module == null)
                    {
                        errorHandler.AddError("Stored module ui not found.");
                    }
                    else
                    {
                        ModuleUIPanel moduleUIPanel = GetModuleUIPanel(module);

                        moduleUIPanel.ModuleUI.SerializedLayout = moduleUIPanelNode;

                        UpdateModule(moduleUIPanel.ModuleUI);

                        // Load som data.
                        try
                        {
                            bool isPublic = moduleUINode.GetPropertyValue<bool>("IsPublic", true);
                            SetVisibility(module, isPublic);
                        }
                        catch (Exception error)
                        {
                            errorHandler.AddError(error.Message);
                        }
                    }
                }
            }

            UpdateLayout();
        }

        internal void UpdateModule(BaseModuleUI moduleUI)
        {
            // If moduleUI do not exist, then it is protected.
            bool isProtected = IsProtected(moduleUI);

            RemoveModule(moduleUI.Module, isProtected);
            ModuleUIPanel moduleUIPanel = InternalAddModuleUIPanel(moduleUI, isProtected);
            moduleUIPanel.LoadStructure();
            moduleUIPanel.InvalidateExpression();
        }

        private void ModuleUi_LayoutChanged()
        {
            UpdateLayout();
        }

        internal void UpdateLayout()
        {
            List<BaseModule> baseModules = new List<BaseModule>();
            foreach (BaseModule mVisibleModule in this.mVisibleModules)
            {
                IList<ParameterProperties> visibleProperties = GroupModule.GetVisibleProperties(mVisibleModule);
                if (visibleProperties.Count > 0)
                {
                    foreach (ParameterProperties visibleProperty in visibleProperties)
                    {
                        if ((visibleProperty as ModuleParameter).ShouldRender)
                        {
                            baseModules.Add(mVisibleModule);
                            break;
                        }
                    }
                }
            }

            int columns = mColumns;
            int rows = mRows;
            if (AutoGrid)
            {
                // Calculate required of columns and rows.
                SharpDepend.Utils.Math.GetOptimizedGrid(baseModules.Count, out rows, out columns);
            }

            double panelWidth = 100.0 / columns;
            double panelHeight = 100.0 / rows;
            double padding = 0.75;
            int i = 0;
            foreach (var module in baseModules)
            {
                var moduleUI = mModules2[module];

                double panelX = (panelWidth + padding) * moduleUI.Panel.ModuleUI.Column;
                double panelY = (panelHeight + padding) * moduleUI.Panel.ModuleUI.Row;

                if (AutoGrid)
                {
                    panelX = (panelWidth + padding) * (i % columns);
                    panelY = (panelHeight + padding) * (i++ / columns);
                }

                moduleUI.X = panelX;
                moduleUI.Y = panelY;
                moduleUI.Width = panelWidth * moduleUI.Panel.ModuleUI.ColumnSpan + (moduleUI.Panel.ModuleUI.ColumnSpan - padding);
                moduleUI.Height = panelHeight * moduleUI.Panel.ModuleUI.RowSpan + (moduleUI.Panel.ModuleUI.RowSpan - padding);
            }
        }

        private void Events_OnScaleChanged()
        {
            UpdateLayout();
        }

        internal ModuleUIPanel GetModuleUIPanel(BaseModule module)
        {
            if (mModules.ContainsKey(module))
            {
                return mModules[module];
            }

            return null;
        }
    }
}
